import numpy as np

# 1. 构建用户-物品评分矩阵 (6用户 x 5物品)
# 0表示未评分
R = np.array([
    [5, 3, 0, 1, 4],
    [4, 0, 0, 1, 5],
    [1, 1, 0, 5, 0],
    [1, 0, 0, 4, 4],
    [0, 1, 5, 4, 0],
    [5, 2, 3, 0, 5]
], dtype=np.float32)

# 2. 计算物品相似度矩阵
def calculate_item_similarity(R):
    """计算物品相似度矩阵
    :params R 用户-物品矩阵
    :return 物品相似度矩阵
    """
    # 计算物品向量范数，也就是计算“用户-物品”矩阵列向量的范数
    item_norms = np.sqrt(np.sum(R**2,axis=0)) 
    
    # 避免除以零的错误
    item_norms[item_norms==0]=1e-10

    # 计算物品间点积
    dot_product = R.T @ R # 一个(n_items,n_items)的对称矩阵
    
    # 计算物品相似度矩阵
    sim_matrix = dot_product/np.outer(item_norms,item_norms) # 矩阵对应元素的除法

    # 将对角线设为0（避免自相似度的影响）
    np.fill_diagonal(sim_matrix,0)

    return sim_matrix

# 3. 生成预测评分
def predict_ratings(R,sim_matrix,user_id):
    """预测用户对所有未评分物品的评分
    :param R 用户-物品矩阵
    :param sim_matrix 物品相似矩阵
    :user_id 目标用户
    
    :return 预测评分""" 

    # 获取用户的评分向量
    user_ratings = R[user_id].copy()

    # 找到已评分和未评分的物品索引
    rated_mask = user_ratings != 0
    unrated_mask = user_ratings == 0
    
    # 计算预测评分，这里首先要找出目标用户未评分物品与其他已评分物品的相似度
    # 然后拿相似度与评分权重平均，作为预测值
    item_sims=sim_matrix[rated_mask][:,unrated_mask] # 得到一个相似度向量（列向量[4,1]）
   
    numerator = item_sims.T @ user_ratings[rated_mask] 
    
    denominator = np.sum(np.abs(item_sims),axis=0)
    
    # 处理分母为零的情况
    denominator[denominator==0] = 1e-10

    pred_ratings = numerator/denominator

    return pred_ratings
   
# 4. 生成推荐
def generate_recommendations(pred_ratings,unrated_indices,top_n=3):
    """生成top_n推荐物品
    :param pred_ratings 预测评分数组
    :param unrated_indices 未评分物品索引
    :param top_n 推荐数量
    :return 推荐物品id和预测评分
    """
    # 按预测评分排序
    sorted_indices = np.argsort(pred_ratings)[::-1] # 由大到小
    top_items = unrated_indices[sorted_indices[:top_n]]
    top_ratings = pred_ratings[sorted_indices[:top_n]]
    
    return list(zip(top_items,top_ratings))




if __name__ == '__main__':
    # 计算物品相似度
    item_sim = calculate_item_similarity(R)
    # print(item_sim)
    # 目标用户（例如用户0）
    target_user = 1

    # 获取目标用户未评分物品索引
    unrated_indices = np.where(R[target_user]==0)[0] # [2]
    
    # 预测评分
    preds = predict_ratings(R,item_sim,target_user)

    # 生成推荐
    recommendations = generate_recommendations(preds,unrated_indices)

    print(f"为目标用户{target_user}生成的推荐:")
    for item,rating in recommendations:
        print(f"物品{item}:预测评分{rating:.2f}")


    # 打印物品相似度矩阵
    print("\n物品相似度矩阵:")
    print(np.round(item_sim,3))

    # 测试所有用户
    print("\n为所有用户生成推荐:")
    for user_id in range(R.shape[0]):
        unrated_indices = np.where(R[user_id]==0)[0]
        if len(unrated_indices)>0:
            preds = predict_ratings(R,item_sim,user_id)
            recommendations = generate_recommendations(preds,unrated_indices)
            print(f"用户{user_id}:{recommendations}")